﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AnaxibiaStudios.Smug.Internal;

namespace SmugTest
{
  [TestClass]
  public class PropertyInfoExTest
  {
    MemberCollection Prep<T>()
    {
      var sb = new SourceBuilder(typeof(T));
      return sb.MemberCollection;
    }

    public class ConflictingItems
    {
      public int Item { get; set; }

      [System.Runtime.CompilerServices.IndexerName("MyItem")]
      public int this[int i] { get { throw new NotImplementedException(); } set { } }
    }

    [TestMethod]
    public void ConflictingItemsTest()
    {
      var members = Prep<ConflictingItems>();
      Assert.AreEqual(0, members.Methods.Count(), "methods count");
      Assert.AreEqual(2, members.Properties.Count(), "methods count");

      var indexer = members.Properties.First(p => p.IsIndexer);
      var item = members.Properties.First(p => !p.IsIndexer);

      Assert.AreEqual("Item", item.Name, "non indexer name");
      Assert.AreEqual("Item", indexer.Name, "indexer name");
      Assert.IsTrue(indexer.IsExplicit || item.IsExplicit, "neither marked explicit");
    }

    public class _0
    {
      public int Foo { get; set; }

      [System.Runtime.CompilerServices.IndexerName("MyItem")]
      public int this[int i] { get { throw new NotImplementedException(); } set { } }
    }

    [TestMethod]
    public void Prop0Test()
    {
      var members = Prep<_0>();
      Assert.AreEqual(0, members.Methods.Count(), "methods count");
      Assert.AreEqual(2, members.Properties.Count(), "methods count");

      var foo = members["Foo"] as PropertyInfoEx;
      Assert.IsNotNull(foo, "foo");
      Assert.IsFalse(foo.IsIndexer);
      Assert.AreEqual("Foo", foo.Name, "Foo name");

      var item = members["Item"] as PropertyInfoEx;
      Assert.IsNotNull(item, "Item");
      Assert.IsTrue(item.IsIndexer);
      Assert.AreEqual("Item", item.Name, "Item name");
    }

    public class _1
    {
      public int Item { get; set; }
    }

    [TestMethod]
    public void ItemPropTest()
    {
      var members = Prep<_1>();
      Assert.AreEqual(0, members.Methods.Count());
      Assert.AreEqual(1, members.Properties.Count());

      var item = members["Item"] as PropertyInfoEx;
      Assert.IsNotNull(item, "item");
      Assert.IsFalse(item.IsIndexer);
    }

    public class _2
    {
      public int set_Foo() { return 1; }
      public void get_Foo(int i) { }
      public int Foo { set { } }

      public int set_Bar() { return 1; }
      public void get_Bar(int i) { }
      public int Bar { get { throw new NotImplementedException(); } }
    }

    [TestMethod]
    public void GetSetPropertyDifferentiationTest()
    {
      var members = Prep<_2>();
      Assert.AreEqual(4, members.Methods.Count());
      Assert.AreEqual(2, members.Properties.Count());

      var set_Foo = members["set_Foo"] as MethodInfoEx;
      var get_Foo = members["get_Foo"] as MethodInfoEx;
      var foo = members["Foo"] as PropertyInfoEx;

      var set_Bar = members["set_Bar"] as MethodInfoEx;
      var get_Bar = members["get_Bar"] as MethodInfoEx;
      var bar = members["Bar"] as PropertyInfoEx;

      Assert.IsNotNull(set_Foo, "set_Foo");
      Assert.IsNotNull(get_Foo, "get_Foo");
      Assert.IsNotNull(foo, "Foo");
      Assert.IsNotNull(set_Bar, "set_Bar");
      Assert.IsNotNull(get_Bar, "get_Bar");
      Assert.IsNotNull(bar, "Bar");
    }

    public interface _3
    {
      double this[int i] { get; set; }
    }

    public interface _4 : _3
    {
      int Item { get; set; }
    }

    [TestMethod]
    public void InterfacedItemIndexerConflicts()
    {
      var members = Prep<_4>();
      Assert.AreEqual(0, members.Methods.Count());
      Assert.AreEqual(2, members.Properties.Count());

      var item = members["Item", false];
      var indexer = members["Item", true];

      Assert.IsNotNull(item, "item, not explicit");
      Assert.IsNotNull(indexer, "indexer, explicit");

      Assert.AreEqual(item.Name, indexer.Name, "names");
    }

    public interface I_SetPart
    {
      int Foo { set; }
    }

    public interface I_GetPart
    {
      int Foo { get; }
    }

    public interface I_GetSet_Joined : I_SetPart, I_GetPart
    {
    }

    [TestMethod]
    public void GetSetJoiningTest()
    {
      var members = new SourceBuilder(typeof(I_GetSet_Joined)).MemberCollection;

      // one or two methods allowed, we don't have to join
      var fooSet = members.Properties.First(p => p.Name == "Foo" && !p.Setter.IsNull).Setter;
      var fooGet = members.Properties.First(p => p.Name == "Foo" && !p.Getter.IsNull).Getter;
    }

    class HitOptions
    {
      public bool ThatIsProperty { get; set; }
      public bool SigsMatch { get; set; }
      public bool ThatIsIndexer { get; set; }
      public bool NamesMatch { get; set; }
      public bool ThisIsIndexer { get; set; }

      public HitOptions(bool thatIsProperty, bool sigsMatch, bool thatIsIndexer, bool namesMatch, bool thisIsIndexer)
      {
        this.ThatIsProperty = thatIsProperty;
        this.SigsMatch = sigsMatch;
        this.ThatIsIndexer = thatIsIndexer;
        this.NamesMatch = namesMatch;
        this.ThisIsIndexer = thisIsIndexer;
      }

      public string HitWrapName()
      {
        return string.Format("{0}+HitWrap_{1}{2}{3}{4}{5}",
          GetType().DeclaringType.FullName,
          _(ThatIsProperty), _(SigsMatch), _(ThatIsIndexer), _(NamesMatch), _(ThisIsIndexer));
      }

      private string _(bool b) { return b ? "1" : "0"; }
    }

    // that.IsProperty  10000
    // sigsMatch        01000
    // that.IsIndexer   00100
    // Names Match      00010
    // this.IsIndexer   00001

    public partial class HitWrap_00000 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }
    public partial class HitWrap_00001 { public interface This { int this[int i] { get; set; } } public interface That { void Bar(double d); } }
  //public partial class HitWrap_00100 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
  //public partial class HitWrap_00101 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
    public partial class HitWrap_01000_Sig { public interface This { int Foo { get; set; } } public interface That { int Bar(); } } // N/A sigsMatch && not prop
    public partial class HitWrap_01001_Sig { public interface This { int this[int i] { get; set; } } public interface That { int Bar(int i); } } // N/A sigsMatch && not prop
  //public partial class HitWrap_01100 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
  //public partial class HitWrap_01101 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
    public partial class HitWrap_10000 { public interface This { int Foo { get; set; } } public interface That { double Bar { get; set; } } }
    public partial class HitWrap_10001 { public interface This { int this[int i] { get; set; } } public interface That { double Bar { get; set; } } }
    public partial class HitWrap_10100 { public interface This { int Foo { get; set; } } public interface That { double this[double d] { get; set; } } }
  //public partial class HitWrap_10101 { public interface This { int this[int i] { get; set; } } public interface That { double this[double d] { get; set; } } } // N/A both indexers && name mismatch
    public partial class HitWrap_11000 { public interface This { int Foo { get; set; } } public interface That { int Bar { get; set; } } }
    public partial class HitWrap_11001_Sig { public interface This { int this[int i] { get; set; } } public interface That { int Bar { get; set; } } } // N/A sig match && indexer mismatch
    public partial class HitWrap_11100_Sig { public interface This { double Foo { get; set; } } public interface That { double this[double d] { get; set; } } } // N/A sig match && indexer mismatch
  //public partial class HitWrap_11101 { public interface This { int this[int i] { get; set; } } public interface That { double this[double d] { get; set; } } } // N/A both indexers && name mismatch

    public partial class HitWrap_00010 { public interface This { int Foo { get; set; } } public interface That { void Foo(double d); } }
    public partial class HitWrap_00011 { public interface This { int this[int i] { get; set; } } public interface That { void Item(double d); } }
  //public partial class HitWrap_00110 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
  //public partial class HitWrap_00111 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
    public partial class HitWrap_01010_Sig { public interface This { int Foo { get; set; } } public interface That { int Foo(); } } // N/A sigsMatch && not prop
    public partial class HitWrap_01011_Sig { public interface This { int this[int i] { get; set; } } public interface That { int Item(int i); } } // N/A sigsMatch && not prop
  //public partial class HitWrap_01110 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
  //public partial class HitWrap_01111 { public interface This { int Foo { get; set; } } public interface That { void Bar(double d); } }// N/A that indexer && not prop
    public partial class HitWrap_10010 { public interface This { int Foo { get; set; } } public interface That { double Foo { get; set; } } }
    public partial class HitWrap_10011 { public interface This { int this[int i] { get; set; } } public interface That { double Item { get; set; } } }
    public partial class HitWrap_10110 { public interface This { int Item { get; set; } } public interface That { double this[double d] { get; set; } } }
    public partial class HitWrap_10111 { public interface This { int this[int i] { get; set; } } public interface That { double this[double d] { get; set; } } }
    public partial class HitWrap_11010 { public interface This { int Foo { get; set; } } public interface That { int Foo { get; set; } } }
    public partial class HitWrap_11011_Sig { public interface This { double this[int i] { get; set; } } public interface That { double Item { get; set; } } } // N/A sig match && indexer mismatch
    public partial class HitWrap_11110_Sig { public interface This { double Item { get; set; } } public interface That { double this[double d] { get; set; } } } // N/A sig match && indexer mismatch
    public partial class HitWrap_11111 { public interface This { int this[int i] { get; set; } } public interface That { int this[int d] { get; set; } } }

    [TestMethod]
    public void SigsCannotMatchTest()
    {
      SigHelper(new HitOptions(false, true , false, false, false));
      SigHelper(new HitOptions(false, true , false, false, true ));

      SigHelper(new HitOptions(true , true , false, false, true ));
      SigHelper(new HitOptions(true , true , true , false, false));

      SigHelper(new HitOptions(false, true , false, true , false));
      SigHelper(new HitOptions(false, true , false, true , true ));

      SigHelper(new HitOptions(true , true , false, true , true ));
      SigHelper(new HitOptions(true , true , true , true , false));
    }

    private void SigHelper(HitOptions hitOptions)
    {
      string typeName = hitOptions.HitWrapName() + "_Sig";
      Type hitWrapType = Type.GetType(typeName);

      hitOptions.SigsMatch = false; // prove they don't match
      var pair = WrapHelper(hitOptions, hitWrapType);
      var th_s = pair.Item1;
      var that = pair.Item2;

      // prove sigs would match if not for the params
      var options = new SignatureOptions
      {
        IgnoreParamNames = true,
        MethodName = string.Empty,
        IgnoreParamTypes = true,
      };

      string thisSig = th_s.BuildSignature(options);
      string thatSig = that.BuildSignature(options);
      bool sigsMatch = thisSig == thatSig;

      Assert.IsTrue(sigsMatch, "sigs match. [{0}] != [{1}]: {2}", thisSig, thatSig, typeName);
    }

    [TestMethod]
    public void HitWrapTests()
    {
      HitWrapHelper(new HitOptions(false, false, false, false, false), PreexistingType.New);
      HitWrapHelper(new HitOptions(false, false, false, false, true ), PreexistingType.New);
      HitWrapHelper(new HitOptions(false, false, true , false, false), null);
      HitWrapHelper(new HitOptions(false, false, true , false, true ), null);
      HitWrapHelper(new HitOptions(false, true , false, false, false), null);
      HitWrapHelper(new HitOptions(false, true , false, false, true ), null);
      HitWrapHelper(new HitOptions(false, true , true , false, false), null);
      HitWrapHelper(new HitOptions(false, true , true , false, true ), null);
      HitWrapHelper(new HitOptions(true , false, false, false, false), PreexistingType.New);
      HitWrapHelper(new HitOptions(true , false, false, false, true ), PreexistingType.New);
      HitWrapHelper(new HitOptions(true , false, true , false, false), PreexistingType.New);
      HitWrapHelper(new HitOptions(true , false, true , false, true ), null);
      HitWrapHelper(new HitOptions(true , true , false, false, false), PreexistingType.New);
      HitWrapHelper(new HitOptions(true , true , false, false, true ), null);
      HitWrapHelper(new HitOptions(true , true , true , false, false), null);
      HitWrapHelper(new HitOptions(true , true , true , false, true ), null);

      HitWrapHelper(new HitOptions(false, false, false, true , false), PreexistingType.Conflicting);
      HitWrapHelper(new HitOptions(false, false, false, true , true ), PreexistingType.Conflicting);
      HitWrapHelper(new HitOptions(false, false, true , true , false), null);
      HitWrapHelper(new HitOptions(false, false, true , true , true ), null);
      HitWrapHelper(new HitOptions(false, true , false, true , false), null);
      HitWrapHelper(new HitOptions(false, true , false, true , true ), null);
      HitWrapHelper(new HitOptions(false, true , true , true , false), null);
      HitWrapHelper(new HitOptions(false, true , true , true , true ), null);
      HitWrapHelper(new HitOptions(true , false, false, true , false), PreexistingType.Conflicting);
      HitWrapHelper(new HitOptions(true , false, false, true , true ), PreexistingType.Conflicting);
      HitWrapHelper(new HitOptions(true , false, true , true , false), PreexistingType.Conflicting);
      HitWrapHelper(new HitOptions(true , false, true , true , true ), PreexistingType.New);
      HitWrapHelper(new HitOptions(true , true , false, true , false), PreexistingType.Exact);
      HitWrapHelper(new HitOptions(true , true , false, true , true ), null);
      HitWrapHelper(new HitOptions(true , true , true , true , false), null);
      HitWrapHelper(new HitOptions(true , true , true , true , true ), PreexistingType.Exact);
    }

    private void HitWrapHelper(HitOptions hitOptions, PreexistingType? expectedHitOptional)
    {
      string typeName = hitOptions.HitWrapName();
      Type hitWrapType = Type.GetType(typeName);

      if (!expectedHitOptional.HasValue)
      {
        Assert.IsNull(hitWrapType, "Found impossible hit type: {0}", typeName);
        return;
      }

      var pair = WrapHelper(hitOptions, hitWrapType);
      var th_s = pair.Item1;
      var that = pair.Item2;

      PreexistingType expectedHit = expectedHitOptional.Value;
      var actual = th_s.Hit(that);
      Assert.AreEqual(expectedHit, actual, "hit: {0}", typeName);
    }

    private Tuple<PropertyInfoEx, MemberInfoEx> WrapHelper(HitOptions hitOptions, Type hitWrapType)
    {
      Assert.IsNotNull(hitWrapType, "Missing hit wrap type: {0}", hitOptions.HitWrapName());
      string typeName = hitWrapType.Name;

      Type thisType = Type.GetType(string.Format("{0}+{1}", hitWrapType, "This"));
      Type thatType = Type.GetType(string.Format("{0}+{1}", hitWrapType, "That"));

      Assert.IsNotNull(thisType, "Missing hit wrap _this_ type: {0}", typeName);
      Assert.IsNotNull(thatType, "Missing hit wrap _that_ type: {0}", typeName);

      var thisBuilder = new SourceBuilder(thisType);
      var thisMembers = thisBuilder.MemberCollection;
      var thatBuilder = new SourceBuilder(thatType);
      var thatMembers = thatBuilder.MemberCollection;

      var th_s = thisMembers.Properties.FirstOrDefault();
      var that = thatMembers.FirstOrDefault(m => m.MemberType != System.Reflection.MemberTypes.Constructor);

      Assert.IsNotNull(th_s, "this.Foo, test[{0}]", typeName);
      Assert.IsNotNull(that, "that.Foo or Bar, test[{0}]", typeName);

      Assert.AreEqual(hitOptions.ThatIsProperty ? 0 : 1, thatMembers.Methods.Count(), "ThatIsProperty method count, test[{0}]", typeName);
      Assert.AreEqual(hitOptions.ThatIsProperty ? 1 : 0, thatMembers.Properties.Count(), "ThatIsProperty property count, test[{0}]", typeName);

      var options = new SignatureOptions
      {
        IgnoreParamNames = true,
        MethodName = string.Empty,
      };

      string thisSig = th_s.BuildSignature(options);
      string thatSig = that.BuildSignature(options);
      bool sigsMatch = thisSig == thatSig;

      var thpx = that as PropertyInfoEx;

      Assert.AreEqual(hitOptions.SigsMatch, sigsMatch, "sigs match. [{0}] != [{1}]: {2}", thisSig, thatSig, typeName);
      Assert.AreEqual(hitOptions.ThatIsIndexer, thpx != null && thpx.IsIndexer, "that is indexer, test[{0}]", typeName);
      Assert.IsTrue(hitOptions.NamesMatch == (th_s.Name == that.Name), "names match, test[{0}]", typeName);
      Assert.AreEqual(hitOptions.ThisIsIndexer, th_s.IsIndexer, "this is indexer, test[{0}]", typeName);

      return Tuple.Create(th_s, that);
    }

    // that.IsProperty  10000 [TRUE]  
    // sigsMatch        01000
    // that.IsIndexer   00100
    // Names Match      00010 [TRUE]
    // this.IsIndexer   00001

    // cannot have sigMatch && !retMatch
    // cannot have sigMatch && (this.IsIndexer == that.IsIndexer)
    // cannot have !sigMatch && retMatch (&& !indexer && name)

    public partial class HitWrap_10010_Return0 { public interface This { int Foo { get; set; } } public interface That { double Foo { get; set; } } }
  //public partial class HitWrap_10010_Return1 { public interface This { int Foo { get; set; } } public interface That { int Foo { get; set; } } }
    public partial class HitWrap_10011_Return0 { public interface This { int this[int i] { get; set; } } public interface That { double Item { get; set; } } }
    public partial class HitWrap_10011_Return1 { public interface This { int this[int i] { get; set; } } public interface That { int Item { get; set; } } }
    public partial class HitWrap_10110_Return0 { public interface This { int Item { get; set; } } public interface That { double this[int i] { get; set; } } }
    public partial class HitWrap_10110_Return1 { public interface This { int Item { get; set; } } public interface That { int this[int i] { get; set; } } }
    public partial class HitWrap_10111_Return0 { public interface This { int this[int i] { get; set; } } public interface That { double this[int i] { get; set; } } }
    public partial class HitWrap_10111_Return1 { public interface This { int this[int i] { get; set; } } public interface That { int this[double i] { get; set; } } }
  //public partial class HitWrap_11010_Return0 { public interface This { int Foo { get; set; } } public interface That { int Foo { get; set; } } }
    public partial class HitWrap_11010_Return1 { public interface This { int Foo { get; set; } } public interface That { int Foo { get; set; } } }
  //public partial class HitWrap_11011_Return0
  //public partial class HitWrap_11011_Return1
  //public partial class HitWrap_11110_Return0
  //public partial class HitWrap_11110_Return1
  //public partial class HitWrap_11111_Return0
    public partial class HitWrap_11111_Return1 { public interface This { int this[int i] { get; set; } } public interface That { int this[int i] { get; set; } } }

    [TestMethod]
    public void HitIndexerTest()
    {
      SigHelper(new HitOptions(true , false, false, true , false), false, PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, false, true , false), true , null);
      SigHelper(new HitOptions(true , false, false, true , true ), false, PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, false, true , true ), true , PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, true , true , false), false, PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, true , true , false), true , PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, true , true , true ), false, PreexistingType.Conflicting);
      SigHelper(new HitOptions(true , false, true , true , true ), true , PreexistingType.New);
      SigHelper(new HitOptions(true , true , false, true , false), false, null);
      SigHelper(new HitOptions(true , true , false, true , false), true , PreexistingType.Exact);
      SigHelper(new HitOptions(true , true , false, true , true ), false, null);
      SigHelper(new HitOptions(true , true , false, true , true ), true , null);
      SigHelper(new HitOptions(true , true , true , true , false), false, null);
      SigHelper(new HitOptions(true , true , true , true , false), true , null);
      SigHelper(new HitOptions(true , true , true , true , true ), false, null);
      SigHelper(new HitOptions(true , true , true , true , true ), true , PreexistingType.Exact);
    }

    private void SigHelper(HitOptions options, bool retMatch, PreexistingType? expectedHitNullable)
    {
      var pair = SuffixHelper(options, retMatch, "_Return", expectedHitNullable);
      if (pair == null) return;
      var th_s = pair.Item1;
      var that = pair.Item2;

      if (!options.SigsMatch && !retMatch && th_s.IsIndexer && that.IsIndexer)
      {
        var sig = new SignatureOptions
        {
          IgnoreParamNames = true,
          MethodName = string.Empty,
          IgnoreReturn = true,
        };

        string thisSig = th_s.BuildSignature(sig);
        string thatSig = that.BuildSignature(sig);

        Assert.AreEqual(thisSig, thatSig, "sigs, type: {0}", options.HitWrapName() + "_Return" + retMatch);
      }
    }

    // that.IsProperty  10000 [TRUE]
    // sigsMatch        01000
    // that.IsIndexer   00100 
    // Names Match      00010 [TRUE]
    // this.IsIndexer   00001

    // cannot have sigMatch && !retMatch
    // cannot have sigMatch && (this.IsIndexer == that.IsIndexer)
    // cannot have !sigMatch && retMatch (&& !indexer && name)

    public partial class HitWrap_10111_NonOverriding0 { public interface This { int this[int i] { get; } } public interface That { int this[double i] { set; } } }
    public partial class HitWrap_10111_NonOverriding1 { public interface This { int this[int i] { get; set; } } public interface That { int this[double i] { get; set; } } }

    public partial class HitWrap_11010_NonOverriding0 { public interface This { int Foo { get; } } public interface That { int Foo { set; } } }
    public partial class HitWrap_11010_NonOverriding1 { public interface This { int Foo { get; set; } } public interface That { int Foo { get; set; } } }

    public partial class HitWrap_11111_NonOverriding0 { public interface This { int this[int i] { get; } } public interface That { int this[int i] { set; } } }
    public partial class HitWrap_11111_NonOverriding1 { public interface This { int this[int i] { get; set; } } public interface That { int this[int i] { get; set; } } }

    [TestMethod]
    public void HitNonOverridingTest()
    {
      NonOverrideHelper(new HitOptions(true, false, true, true, true), false, PreexistingType.New);
      NonOverrideHelper(new HitOptions(true, false, true, true, true), true, PreexistingType.New);

      NonOverrideHelper(new HitOptions(true, true, false, true, false), false, PreexistingType.Conflicting);
      NonOverrideHelper(new HitOptions(true, true, false, true, false), true, PreexistingType.Exact);

      NonOverrideHelper(new HitOptions(true, true, true, true, true), false, PreexistingType.Conflicting);
      NonOverrideHelper(new HitOptions(true, true, true, true, true), true, PreexistingType.Exact);
    }

    private Tuple<PropertyInfoEx, PropertyInfoEx> NonOverrideHelper(HitOptions options, bool retMatch, PreexistingType? expectedHitNullable)
    {
      return SuffixHelper(options, retMatch, "_NonOverriding", expectedHitNullable);
    }

    private Tuple<PropertyInfoEx, PropertyInfoEx> SuffixHelper(HitOptions options, bool retMatch, string suffix, PreexistingType? expectedHitNullable)
    {
      string typeName = options.HitWrapName() + suffix + (retMatch ? "1" : "0");
      Type hitWrapType = Type.GetType(typeName);

      if (!expectedHitNullable.HasValue)
      {
        Assert.IsNull(hitWrapType, "found impossible: ", typeName);
        return null;
      }

      Assert.IsNotNull(hitWrapType, "missing expected wrap type: {0}", typeName);
      var expectedHit = expectedHitNullable.Value;
      var pair = WrapHelper(options, hitWrapType);
      var th_s = pair.Item1;
      var that = pair.Item2 as PropertyInfoEx;

      Assert.AreEqual(retMatch, 
        th_s.ReturnType == that.ReturnType &&
        MemberInfoEx.SharedAccess(th_s.Getter, that.Getter) &&
        MemberInfoEx.SharedAccess(th_s.Setter, that.Setter), 
        "ret match type: {0}", typeName);

      var hit = th_s.Hit(that);
      Assert.AreEqual(expectedHit, hit, "hit: {0}", typeName);

      return Tuple.Create(th_s, that);
    }
  }
}
